﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using Patcher.Main;
using Patcher.Misc;

#if WIN32
using DWORD_PTR = System.UInt32;
#elif WIN64
using DWORD_PTR = System.UInt64;
#endif

namespace Patcher.Forms
{
    public partial class frmMain : Form
    {

        private Dictionary<string, Process> ProcessList = new Dictionary<string, Process>();

        #region Form events
        public frmMain()
        {
            InitializeComponent();
        }

        private void frmMain_Load( object sender, EventArgs e )
        {
            Globals.CheckUpdatesForm.CheckUpdates();
            Globals.CheckUpdatesForm.ShowDialog( this );

            RefreshProcessList();
        }

        private void menuOptions_File_Exit_Click( object sender, EventArgs e )
        {
            Application.Exit();
        }

        private void menuOptions_Tools_Patch_Click( object sender, EventArgs e )
        {
            PatchCurrentProcess();
        }

        private void menuOptions_Tools_Unpatch_Click( object sender, EventArgs e )
        {
            UnpatchCurrentProcess();
        }

        private void menuOptions_Tools_Refresh_Click( object sender, EventArgs e )
        {
            RefreshProcessList();
        }

        private void menuOptions_Help_CheckUpdates_Click( object sender, EventArgs e )
        {
            Globals.CheckUpdatesForm.ResetDownloadQueue();
            Globals.CheckUpdatesForm.CheckUpdates();
            Globals.CheckUpdatesForm.ShowDialog( this );
        }

        private void menuOptions_Help_About_Click( object sender, EventArgs e )
        {
            Globals.AboutForm.ShowDialog( this );
        }

        private void lstProcesses_DoubleClick( object sender, EventArgs e )
        {
            TogglePatchCurrentProcess();
        }
        #endregion

        /// <summary>
        /// Refreshes the process list.
        /// </summary>
        private void RefreshProcessList()
        {
            ProcessList.Clear();
            lstProcesses.Items.Clear();

            foreach( Process pProcess in Process.GetProcessesByName( "eso" ) )
            {
                string sPID = pProcess.Id.ToString();
                ProcessList.Add( sPID, pProcess );

                string sMD5Hash = Functions.MD5( Functions.GetMainModulePath( pProcess ) );
                string sGameVersion = "Unknown";

                bool bIsPatched = false;
                string sPlayerName = "Not in-game";

                if( Globals.ClientHashes.ContainsKey( sMD5Hash ) )
                {
                    sGameVersion = Globals.ClientHashes[sMD5Hash];

                    IntPtr hProcess = Memory.OpenProcess( pProcess.Id );
                    DWORD_PTR dwBaseAddress = (DWORD_PTR) pProcess.MainModule.BaseAddress;

                    bIsPatched = Memory.Read<bool>( hProcess, dwBaseAddress + Offsets.Patches.DisplayWatermark ) == false;

                    bool bIsInGame = Memory.Read<bool>( hProcess, dwBaseAddress + Offsets.General.IsInGame );
                    if( bIsInGame )
                    {
                        DWORD_PTR dwCurrentManager, dwLocalObject, dwNamePointer;

                        dwCurrentManager = Memory.Read<DWORD_PTR>( hProcess, dwBaseAddress + Offsets.ObjectManager.CurrentManager1 );
                        dwCurrentManager = Memory.Read<DWORD_PTR>( hProcess, dwCurrentManager + Offsets.ObjectManager.CurrentManager2 );

                        dwLocalObject = Memory.Read<DWORD_PTR>( hProcess, dwCurrentManager + Offsets.ObjectManager.LocalObject );

                        dwNamePointer = Memory.Read<DWORD_PTR>( hProcess, dwLocalObject + Offsets.ObjectManager.ObjectName1 );
                        dwNamePointer = Memory.Read<DWORD_PTR>( hProcess, dwNamePointer + Offsets.ObjectManager.ObjectName2 );

                        sPlayerName = Memory.Read<string>( hProcess, dwNamePointer, CharSet.Ansi, 25 );
                    }

                    Memory.CloseHandle( hProcess );
                }

                ListViewItem pListItem = lstProcesses.Items.Add( sPID );
                pListItem.SubItems.Add( sGameVersion );
                pListItem.SubItems.Add( sPlayerName );
                pListItem.SubItems.Add( bIsPatched ? "Yes" : "No" );
            }
        }

        /// <summary>
        /// Patches or unpatches the selected process.
        /// </summary>
        private void TogglePatchCurrentProcess()
        {
            if( lstProcesses.SelectedItems.Count != 1 )
                return;

            if( lstProcesses.SelectedItems[0].SubItems[3].Text == "No" )
                PatchCurrentProcess();
            else
                UnpatchCurrentProcess();
        }

        /// <summary>
        /// Patches the selected process.
        /// </summary>
        private void PatchCurrentProcess()
        {
            if( lstProcesses.SelectedItems.Count != 1 || lstProcesses.SelectedItems[0].SubItems[3].Text != "No" )
                return;

            if( lstProcesses.SelectedItems[0].SubItems[1].Text != Globals.GameVersion )
            {
                MessageBox.Show( "This game version is not yet supported! Please check for updates periodically.",
                                    "Unsupported version", MessageBoxButtons.OK, MessageBoxIcon.Error );
                return;
            }
            
            if( PatchProcess( ProcessList[lstProcesses.SelectedItems[0].Text] ) )
                lstProcesses.SelectedItems[0].SubItems[3].Text = "Yes";
        }

        /// <summary>
        /// Unpatches the selected process.
        /// </summary>
        private void UnpatchCurrentProcess()
        {
            if( lstProcesses.SelectedItems.Count != 1 || lstProcesses.SelectedItems[0].SubItems[3].Text != "Yes" )
                return;

            if( UnpatchProcess( ProcessList[lstProcesses.SelectedItems[0].Text] ) )
                lstProcesses.SelectedItems[0].SubItems[3].Text = "No";
        }

        /// <summary>
        /// Patches a specified process.
        /// </summary>
        private bool PatchProcess( Process pProcess )
        {
            IntPtr hProcess = Memory.OpenProcess( pProcess.Id );
            if( hProcess == IntPtr.Zero )
                return false;

            DWORD_PTR dwBaseAddress = (DWORD_PTR) pProcess.MainModule.BaseAddress;
            bool bResult = Memory.Write( hProcess, dwBaseAddress + Offsets.Patches.DisplayWatermark, false );

            Memory.CloseHandle( hProcess );

            if( bResult )
                Functions.PlaySound( "Resources\\Success.wav" );
            else
                Functions.PlaySound( "Resources\\Error.wav" );

            return bResult;
        }

        /// <summary>
        /// Unpatches a specified process.
        /// </summary>
        private bool UnpatchProcess( Process pProcess )
        {
            IntPtr hProcess = Memory.OpenProcess( pProcess.Id );
            if( hProcess == IntPtr.Zero )
                return false;

            DWORD_PTR dwBaseAddress = (DWORD_PTR) pProcess.MainModule.BaseAddress;
            bool bResult = Memory.Write( hProcess, dwBaseAddress + Offsets.Patches.DisplayWatermark, true );

            Memory.CloseHandle( hProcess );

            if( bResult )
                Functions.PlaySound( "Resources\\Success.wav" );
            else
                Functions.PlaySound( "Resources\\Error.wav" );

            return bResult;
        }

    }
}
